using System;
using System.Text;

namespace csammisrun.OscarLib.Utility
{
  /// <summary>
  /// Provides static methods for processing messages in SNAC family 0x0002 -- location services
  /// </summary>
  internal static class SNAC02
  {
    private const int PARAMETER_PROFILELENGTH = 0x0001;
    private const int PARAMETER_MAXCAPABILITIES = 0x0002;

    private const int STATUS_PROFILE_ENCODING = 0x0001;
    private const int STATUS_PROFILE = 0x0002;
    private const int STATUS_AWAYMSG_ENCODING = 0x0003;
    private const int STATUS_AWAYMSG = 0x0004;
    private const int STATUS_CAPABILITIES = 0x0005;

    private const int DIRECTORY_FIRSTNAME = 0x0001;
    private const int DIRECTORY_MIDDLENAME = 0x0002;
    private const int DIRECTORY_LASTNAME = 0x0003;
    private const int DIRECTORY_MAIDENNAME = 0x0004;
    private const int DIRECTORY_COUNTRY = 0x0006;
    private const int DIRECTORY_STATE = 0x0007;
    private const int DIRECTORY_CITY = 0x0008;
    private const int DIRECTORY_NICKNAME = 0x000C;
    private const int DIRECTORY_ZIPCODE = 0x000D;
    private const int DIRECTORY_ADDRESS = 0x0021;

    private const int INTERESTS_ALLOWSEARCH = 0x001A;
    private const int INTERESTS_INTEREST = 0x000B;

    /// <summary>
    /// Sends a request for parameter information -- SNAC(02,02)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    public static void RequestParametersList(Session sess)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.LocationServices;
      sh.FamilySubtypeID = (ushort)LocationServices.LimitationsParamsRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, null);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Processes the parameter information sent by the server -- SNAC(02,03)
    /// </summary>
    /// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(02,03)</param>
    public static void ProcessParametersList(DataPacket dp)
    {
      byte[] SNACData = dp.Data;

      int index = 0;

      while (index < SNACData.Length)
      {
        ushort key = Marshal.ByteArrayToUshort(SNACData, ref index);
        ushort keylength = Marshal.ByteArrayToUshort(SNACData, ref index);
        switch (key)
        {
          case SNAC02.PARAMETER_PROFILELENGTH:
            dp.ParentSession.Limits.MaxProfileLength = 
              Marshal.ByteArrayToUshort(SNACData, ref index);
            break;
          case SNAC02.PARAMETER_MAXCAPABILITIES:
            dp.ParentSession.Limits.MaxCapabilities = 
              Marshal.ByteArrayToUshort(SNACData, ref index);
            break;
          default:
            index += keylength;
            break;
        }
      }

      SNAC02.ReportCapabilitiesList(dp.ParentSession);
      dp.ParentSession.ParameterSetArrived();
    }

    /// <summary>
    /// Updates the client's profile and / or away message -- SNAC(02,04)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="profile">The profile string</param>
    /// <param name="profile_encoding">The <see cref="System.Text.Encoding"/> of the profile string</param>
    /// <param name="awaymessage">The away message string</param>
    /// <param name="awaymessage_encoding">The <see cref="System.Text.Encoding"/> of the away message string</param>
    /// <remarks>
    /// Usually the profile and away message are set seperately. To set only the away message or the profile,
    /// set the other two arguments to null.
    /// <code>
    /// // Set the away message only
    /// SNAC02.SetProfileAwayMessage(ci, null, null, awaymessage, encoding);
    /// // Set the profile only
    /// SNAC02.SetProfileAwayMessage(ci, profile, encoding, null, null);
    /// </code>
    /// </remarks>
    public static void SetProfileAwayMessage(
      Session sess,
      string profile, Encoding profile_encoding,
      string awaymessage, Encoding awaymessage_encoding)
    {
      byte[] profile_encoding_format = null;
      byte[] profilearray = null;
      byte[] awaymessage_encoding_format = null;
      byte[] awaymessagearray = null;
      int tlvs = 0;

      // Build the SNAC header
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.LocationServices;
      sh.FamilySubtypeID = (ushort)LocationServices.SetUserInformation;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      // Get byte arrays for the profile and away message encodings and messages
      if (profile != null)
      {
        profile_encoding_format = Marshal.StringToByteArray(
          Marshal.EncodingToAolMime(profile_encoding), Marshal.ASCII);
        profilearray = profile_encoding.GetBytes(profile);
        tlvs += 2;
      }
      if (awaymessage != null)
      {
        awaymessage_encoding_format = Marshal.StringToByteArray(
          Marshal.EncodingToAolMime(awaymessage_encoding), Marshal.ASCII);
        awaymessagearray = awaymessage_encoding.GetBytes(awaymessage);
        tlvs += 2;
      }

      // Create the TLV array
      TLV[] tlvarray = new TLV[tlvs];
      int tlvindex = 0;
      if (profile != null)
      {
        tlvarray[tlvindex] = TLVMarshal.MakeTLV(
          SNAC02.STATUS_PROFILE_ENCODING, profile_encoding_format);
        tlvarray[tlvindex + 1] = TLVMarshal.MakeTLV(
          SNAC02.STATUS_PROFILE, profilearray);
        tlvindex += 2;
      }
      if (awaymessage != null)
      {
        tlvarray[tlvindex] = TLVMarshal.MakeTLV(
          SNAC02.STATUS_AWAYMSG_ENCODING, awaymessage_encoding_format);
        tlvarray[tlvindex + 1] = TLVMarshal.MakeTLV(
          SNAC02.STATUS_AWAYMSG, awaymessagearray);
      }

      // Send off the SNAC
      DataPacket dp = Marshal.BuildDataPacket(sess, sh, TLVMarshal.TLVsToByteArray(tlvarray));
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Sends a list of the client's capabilities to the server -- SNAC(02,04)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    public static void ReportCapabilitiesList(Session sess)
    {
      if (sess.ClientCapabilities == Capabilities.None)
        return;

      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.LocationServices;
      sh.FamilySubtypeID = (ushort)LocationServices.SetUserInformation;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      // Build the capabilities list, TLV type 0x0005
      byte[] caps = CapabilityProcessor.GetCapabilityArray(sess.ClientCapabilities);
      TLV tlv = TLVMarshal.MakeTLV(SNAC02.STATUS_CAPABILITIES, caps);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, TLVMarshal.TLVsToByteArray(new TLV[] { tlv }));
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Requests user information from the server, one block at a time -- SNAC(02,05)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="screenname">The screenname to get information about</param>
    /// <param name="requesttype">The type of information to retrieve</param>
    public static void RequestBasicUserInfo(Session sess, string screenname, BasicUserInfoRequest requesttype)
    {
      // Build SNAC(02,05)
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.LocationServices;
      sh.FamilySubtypeID = (ushort)LocationServices.BasicInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      Encoding enc = Marshal.ASCII;
      byte screennamelength = (byte)enc.GetByteCount(screenname);

      int index = 0;
      byte[] buffer = new byte[3 + screenname.Length];
      Marshal.InsertUshort(buffer, (ushort)requesttype, ref index);
      buffer[index++] = screennamelength;
      Marshal.InsertString(buffer, screenname, enc, ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Processes user information sent by the server -- SNAC(02,06)
    /// </summary>
    /// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(02,06)</param>
    public static void ProcessUserInfo(DataPacket dp)
    {
      byte[] SNACData = dp.Data;
      int index = 0;
      ushort key, keylength;

      if (dp.SNAC.Flags != 0)
      {
        Logging.DumpFLAP(dp.Data, "You've got to be shitting me");
      }

      // Apparently, the userinfo block will always be first,
      // and then possibly TLVs 0x0001 - 0x0005, depending on the request

      UserInfo ui = Marshal.GetUserinfoBlock(SNACData, ref index);

      byte[] awaymessage = null;
      Encoding awaymessageencoding = Marshal.ASCII;
      byte[] profile = null;
      Encoding profileencoding = Marshal.ASCII;
      byte[] capabilities = null;
      Capabilities caps = Capabilities.None;

      while (index < SNACData.Length)
      {
        key = Marshal.ByteArrayToUshort(SNACData, ref index);
        keylength = Marshal.ByteArrayToUshort(SNACData, ref index);
        switch (key)
        {
          case SNAC02.STATUS_PROFILE_ENCODING:
            profileencoding = Marshal.AolMimeToEncoding(
              Marshal.ByteArrayToString(SNACData, ref index, keylength, Marshal.ASCII));
            break;
          case SNAC02.STATUS_PROFILE:
            profile = new byte[keylength];
            Marshal.CopyArray(SNACData, profile, ref index);
            break;
          case SNAC02.STATUS_AWAYMSG_ENCODING:
            awaymessageencoding = Marshal.AolMimeToEncoding(
              Marshal.ByteArrayToString(SNACData, ref index, keylength, Marshal.ASCII));
            break;
          case SNAC02.STATUS_AWAYMSG:
            awaymessage = new byte[keylength];
            Marshal.CopyArray(SNACData, awaymessage, ref index);
            break;
          case SNAC02.STATUS_CAPABILITIES:
            capabilities = new byte[keylength];
            Marshal.CopyArray(SNACData, capabilities, ref index);
            caps = CapabilityProcessor.ProcessCLSIDList(capabilities);
            break;
          default:
            index += keylength;
            break;
        }
      }

      UserInfoResponse uir = new UserInfoResponse();
      uir.Info = ui;
      if (profile != null)
      {
        uir.Profile = profileencoding.GetString(profile, 0, profile.Length);
        uir.ProfileEncoding = profileencoding;
      }
      if (awaymessage != null)
      {
        uir.AwayMessage = awaymessageencoding.GetString(awaymessage, 0, awaymessage.Length);
        uir.AwayMessageEncoding = awaymessageencoding;
      }
      uir.ClientCapabilities = caps;

      // Alert the session that information has come back
      dp.ParentSession.OnUserInfoReceived(uir);
    }

    /// <summary>
    /// Sets the user's directory information -- SNAC(02,09)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="allow"><c>true</c> if other users may search this information, <c>false</c> if not</param>
    /// <param name="firstname">A first name</param>
    /// <param name="middlename">A middle name</param>
    /// <param name="lastname">A last name</param>
    /// <param name="maidenname">A maiden name</param>
    /// <param name="nickname">A nickname</param>
    /// <param name="city">A city</param>
    /// <param name="state">A state</param>
    /// <param name="country">A country (two-letter code)</param>
    /// <param name="zip">A ZIP code</param>
    /// <param name="address">An address</param>
    public static void SetDirectoryInformation(
      Session sess, bool allow,
      string firstname, string middlename, string lastname, string maidenname, string nickname,
      string city, string state, string country, string zip, string address)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.LocationServices;
      sh.FamilySubtypeID = (ushort)LocationServices.UpdateDirectoryInfoRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      TLV[] tlvs = new TLV[11];
      tlvs[0] = TLVMarshal.MakeTLV(
        0x001A,
        (allow) ? Marshal.UShortToByteArray(0x0001) : Marshal.UShortToByteArray(0x0000));
      tlvs[1] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_FIRSTNAME, Marshal.StringToByteArray(firstname, Marshal.ASCII));
      tlvs[2] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_MIDDLENAME, Marshal.StringToByteArray(middlename, Marshal.ASCII));
      tlvs[3] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_LASTNAME, Marshal.StringToByteArray(lastname, Marshal.ASCII));
      tlvs[4] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_MAIDENNAME, Marshal.StringToByteArray(maidenname, Marshal.ASCII));
      tlvs[5] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_COUNTRY, Marshal.StringToByteArray(country, Marshal.ASCII));
      tlvs[6] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_STATE, Marshal.StringToByteArray(state, Marshal.ASCII));
      tlvs[7] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_CITY, Marshal.StringToByteArray(city, Marshal.ASCII));
      tlvs[8] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_NICKNAME, Marshal.StringToByteArray(nickname, Marshal.ASCII));
      tlvs[9] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_ZIPCODE, Marshal.StringToByteArray(zip, Marshal.ASCII));
      tlvs[10] = TLVMarshal.MakeTLV(
        SNAC02.DIRECTORY_ADDRESS, Marshal.StringToByteArray(address, Marshal.ASCII));

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, TLVMarshal.TLVsToByteArray(tlvs));
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Processes a reply to a directory or interests information update -- SNAC(02,0A) and SNAC(02,10)
    /// </summary>
    /// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(02,0A)</param>
    public static void ProcessUpdateResult(DataPacket dp)
    {
      byte[] SNACData = dp.Data;

      ushort result = Marshal.ByteArrayToUshort(SNACData, 0);

      dp.ParentSession.OnDirectoryUpdateAcknowledged((result == 1));
    }

    /// <summary>
    /// Registers the user's screenname with the Location service -- SNAC(02,0B)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <remarks>
    /// The function of this SNAC is unknown, but the official AIM client (5.9.3861) sends it sometime
    /// after SNAC(01,02) is sent.
    /// </remarks>
    public static void SetSelfLocation(Session sess)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.LocationServices;
      sh.FamilySubtypeID = (ushort)0x000B;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      Encoding enc = Marshal.ASCII;
      byte screennamelength = (byte)enc.GetByteCount(sess.ScreenName);

      int index = 0;
      byte[] buffer = new byte[1 + screennamelength];
      buffer[index++] = screennamelength;
      Marshal.InsertString(buffer, sess.ScreenName, enc, ref index);

      // This SNAC expects a response in SNAC(02,0C)
      sess.StoreRequestID(sh.RequestID, null);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }

    /// <summary>
    /// Processes a reply to SNAC(02,0B) -- SNAC(02,0C)
    /// </summary>
    /// <param name="dp">A <see cref="DataPacket"/> object with a buffer containing SNAC(02,0C)</param>
    public static void ProcessSelfLocationReply(DataPacket dp)
    {
      byte[] SNACData = dp.Data;

      dp.ParentSession.RetrieveRequestID(dp.SNAC.RequestID);
    }


    /// <summary>
    /// Sets the user's interests list -- SNAC(02,0F)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="allow"><c>true</c> if other users may search this information, <c>false</c> if not</param>
    /// <param name="interests">An array of interest names</param>
    /// <remarks>
    /// OSCAR allows a user to set up to five interests. If <paramref name="interests"/> contains
    /// more than five items, only the first five are used.
    /// </remarks>
    public static void SetInterestsInformation(Session sess, bool allow, string[] interests)
    {
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.LocationServices;
      sh.FamilySubtypeID = (ushort)LocationServices.UpdateInterestsRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      int num_tlv = 1 + ((interests.Length <= 5) ? interests.Length : 5);
      TLV[] tlvs = new TLV[num_tlv];

      tlvs[0] = TLVMarshal.MakeTLV(
        SNAC02.INTERESTS_ALLOWSEARCH,
        (allow) ? Marshal.UShortToByteArray(0x0001) : Marshal.UShortToByteArray(0x0000));

      for (int i = 0; i < interests.Length && i < 5; i++)
      {
        tlvs[i + 1] = TLVMarshal.MakeTLV(
          SNAC02.INTERESTS_INTEREST, Marshal.StringToByteArray(interests[i], Marshal.ASCII));
      }

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, TLVMarshal.TLVsToByteArray(tlvs));
      SNACFunctions.BuildFLAP(dp);
    }



    /// <summary>
    /// Requests user information from the server -- SNAC(02,15)
    /// </summary>
    /// <param name="sess">A <see cref="Session"/> object</param>
    /// <param name="screenname">The screenname to get information about</param>
    /// <param name="requesttype">The type of information to retrieve</param>
    /// <remarks>The <cparam>requesttype</cparam> field can be a single <see cref="UserInfoRequest"/>,
    /// or several ORed together</remarks>
    public static void RequestUserInfo(Session sess, string screenname, UserInfoRequest requesttype)
    {
      // Build SNAC(02,15)
      SNACHeader sh = new SNACHeader();
      sh.FamilyServiceID = (ushort)SNACFamily.LocationServices;
      sh.FamilySubtypeID = (ushort)LocationServices.ExtendedInformationRequest;
      sh.Flags = 0x0000;
      sh.RequestID = sess.GetNextRequestID();

      byte screennamelength = (byte)Marshal.ASCII.GetByteCount(screenname);

      int index = 0;
      byte[] buffer = new byte[5 + screennamelength];
      Marshal.InsertUint(buffer, (uint)requesttype, ref index);
      buffer[index++] = screennamelength;
      Marshal.InsertString(buffer, screenname, Marshal.ASCII, ref index);

      DataPacket dp = Marshal.BuildDataPacket(sess, sh, buffer);
      SNACFunctions.BuildFLAP(dp);
    }
  }
}
